#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <dht11.h>
#include <LiquidCrystal.h>
#include <ArduinoJson.h>

// 土壤温湿度传感器引脚模块
#define MH_A0 A0    // 定义AO 引脚 为 IO-A0  
#define MH_DO D2    // 定义DO 引脚 为 IO-D2
int soilMoistureValue[2];  

//根据引脚初始化库
LiquidCrystal lcd(D0, D1, D8, D7, D6, D5);      // D3用于反向控制, D4已经被DHT11模块占用

String tempAndHumiValue[2];
String globalTimeString;
//int didiPin = D2;

// setting for wifi module
const char* ssid = "ZUCC-LAB";
const char* password = "13141516";
String wifiMacAddress;
String deviceInfo[3];         // device info, 0 is hostname, 1 is wifi mac address, 2 is ip address
WiFiClient espClient;

// setting for mqtt module
const char* mqtt_server = "mqtt.cenyol.com";
//PubSubClient client(espClient);
char msg[200];
String mqttPasswd = "qwe123";   // mqtt auth password
String finalPayLoadMsg;
void callback(char* topic, byte* payload, unsigned int length);       // Callback function header
PubSubClient client("mqtt.cenyol.com", 1883, callback, espClient);

int LED_D3 = D3;             // ESP8266 WiFi模块左上角的蓝色LED
void setup(){
  lcd.begin(16, 2);
  lcd.print("Welcome, Master!");
  
  Serial.begin(9600);
  pinMode(LED_D3, OUTPUT);
  
  setup_wifi(deviceInfo);
  init_connect();       // 初始连接, 订阅一下专属主题以便受控,发送本设备信息到服务器
  
  delay(2000);  // 确保欢迎信息至少显示2秒
  lcd.clear();
}

// TODO 记得做溢出处理,millis大概60天一个轮回
int tempAndHumiDetectMillis = 0;
int currentMillis = millis();             // 获取系统开机至今的毫秒数
int intervalMillis = 1000 * 60;
void loop() {
  if (!client.connected()) {
    mqtt_connect();
  }
  client.loop();

  currentMillis = millis();
  if(currentMillis > tempAndHumiDetectMillis + intervalMillis) {
    soilMoisture();    
    finalPayLoadMsg = "{\"mac\":\"" + wifiMacAddress + "\", \"data\":[" + getTempAndHumiJsonString() + 
        ",{\"moisture:\":"+soilMoistureValue[1]+"}]}";
    finalPayLoadMsg.toCharArray(msg, 200);              // ref https://stackoverflow.com/a/7391187/5509827
    client.publish("$data", msg);
    tempAndHumiDetectMillis = currentMillis;

//    displayTemp();
    
    lcd.setCursor(0, 0);
    lcd.print("T:" + tempAndHumiValue[0]);
    lcd.print((char)223);
    lcd.print("C  H:" + tempAndHumiValue[1] + "%");
  }
}


void displayTemp() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("T:" + tempAndHumiValue[0]);
  lcd.print((char)223);
  lcd.print("C  H:" + tempAndHumiValue[1] + "%");
    
  lcd.setCursor(0, 1);
  lcd.print(globalTimeString);
}

// string to char*, ref: https://coderwall.com/p/zfmwsg/arduino-string-to-char
char* str2char(String string){
    if(string.length()!=0){
        char *p = const_cast<char*>(string.c_str());
        return p;
    }
}

//void didi() {   // 蜂鸣器发声
//  for(int i = 0; i < 1; i++) {
//    digitalWrite(didiPin, HIGH);
//    delay(1000);
//    digitalWrite(didiPin, LOW);
//    delay(1000);
//  }
//}
/*****************1. code for common function module end*****************/




/*****************2. code for mqtt module start*****************/
void init_connect() {
  if (mqtt_connect()) {
    client.publish("$new",str2char("{\"host\":\"" + deviceInfo[0] + "\",\"mac\":\"" + deviceInfo[1] + "\",\"ip\":\"" + String(deviceInfo[2]) + "\"}"));
  }
}

boolean mqtt_connect() {
  while (!client.connected()) {       // MQTT重连机制,类似WiFi的重连机制  Loop until we're reconnected
    reconnect_wifi();                 // 先确保WiFi已经连上了, Loop until we reconnect to wifi
    for(int i = 0; i < 5; i++) {
      if (client.connect(str2char(deviceInfo[1]), str2char(deviceInfo[1]), str2char(mqttPasswd))) {
        client.subscribe("$time");
        client.subscribe("$weather");
        return true;
      }else{
        delay(5000);                  // 暂停5秒再重连
      }
    }
    delay(5 * 60 * 1000);             // 如果连接5次还没连上,那就休息5分钟再继续
  }
  return true;
}

String cmd;
void callback(char* topic, byte* payload, unsigned int length) {
  StaticJsonBuffer<200> jsonBuffer;
//  Serial.println(topic);

  if((char)topic[1] == 'w' && (char)topic[2] == 'e') {  // 天气预报
    JsonObject& root = jsonBuffer.parseObject(payload);
    if (root.success()) {
      String text = root["text"];
      String temp = root["temperature"];
//      lcd.setCursor(0, 1);
//      lcd.print(string);
      Serial.println(text + temp);
    }
  }
  
  if((char)topic[1] == 't' && (char)topic[2] == 'i') {  // 时间同步
    JsonObject& root = jsonBuffer.parseObject(payload);
    if (root.success()) {
      String string = root["string"];
      int type = root["type"];
      lcd.setCursor(0, 1);
      lcd.print(string);
    }
  }

//  if(topic == str2char("$client/" + wifiMacAddress)) {  // 反向控制
  if((char)topic[1] == 'c' && (char)topic[2] == 'l') {
      
    // Switch on the LED if an 1 was received as first character
    if ((char)payload[0] == '1') {
      cmd = "1";
      digitalWrite(LED_D3, HIGH);
    } else {
      cmd = "0";
      digitalWrite(LED_D3, LOW);  // Turn the LED off by making the voltage HIGH
    }
    
    JsonObject& root = jsonBuffer.createObject();
    root["mac"] = wifiMacAddress;
    root["cmd"] = cmd;
    root["result"] = "Operation success!";
    finalPayLoadMsg = "";
    root.printTo(finalPayLoadMsg);
    
    finalPayLoadMsg.toCharArray(msg, 200);
    client.publish("$feedback", msg);   // publish feedback info to the topic "/relay/feedback"
  }
  
  if(topic == "$test") {
    
  }
}
/*****************2. code for mqtt module end*****************/




/*****************3. code for dht11 module start*****************/
// 获取温湿度信息
dht11 DHT11;
#define DHT11PIN D0 //使用4引脚口作为OneWire接线引脚
String tempAndHumiJsonStr;
String getTempAndHumiJsonString() {
  tempAndHumiJsonStr = "";
  if(DHTLIB_OK==DHT11.read(DHT11PIN)){
    tempAndHumiValue[0] = String(DHT11.temperature);
    tempAndHumiValue[1] = String(DHT11.humidity);
    tempAndHumiJsonStr += "{\"type_id\":1, \"value\":" + String(DHT11.temperature) + "},";
    tempAndHumiJsonStr += "{\"type_id\":2, \"value\":" + String(DHT11.humidity) + "}";
//    return ",temp:" + String(DHT11.temperature) + ",humi:" + String(DHT11.humidity);
  }
  return tempAndHumiJsonStr;
}
/*****************3. code for dht11 module end*****************/




/*****************4. code for wifi module start*****************/
// 设置并连接WiFi
boolean setup_wifi(String info[]) {
  if(WiFi.status() == WL_CONNECTED) {
//    Serial.println("Checking WiFi status before connect. Status Code: " + WiFi.status());
    return true;
  }
    
  delay(10);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  for (int i = 0; (WiFi.status() != WL_CONNECTED) && i < 10; i++) {
    delay(500);
    Serial.print(".");
    if(i >= 10) return false;       // 如果等了10次都还没连上, 那么结束本次连接
  }

  // 获取WiFi模块的Mac地址,参考:https://github.com/esp8266/Arduino/issues/313#issuecomment-104945593
  unsigned char mac[6];
  wifiMacAddress = "";
  WiFi.macAddress(mac);
  for (int i = 0; i < 6; ++i)
    wifiMacAddress += String(mac[i], 16);
  Serial.println("WiFi connected.");
  Serial.print("Hostname: ");
  Serial.print(WiFi.hostname());
  
  Serial.print(", Mac address: " + wifiMacAddress + ", IP address: ");
  Serial.println(WiFi.localIP());
  
  info[0] = WiFi.hostname();
  info[1] = wifiMacAddress;
  info[2] = ipToString(WiFi.localIP());           // char array to string,ref: http://forum.arduino.cc/index.php?topic=152683.msg1146049#msg1146049
  
  Serial.println(WiFi.status());
  return WiFi.status() == WL_CONNECTED;
}

// WiFi重连机制,在连接失败的时候增加一些等待时间,以减少过多不必要的频繁重连,应该能降低能耗。  WiFi status ref: https://www.arduino.cc/en/Reference/WiFiStatus
void reconnect_wifi() {
  while(WiFi.status() != WL_CONNECTED) {// Loop until we're reconnected
    Serial.println("WiFi re-connecting");
    for(int j = 0; j < 5; j++) {
      for(int i = 0; i < 5; i++) {
        // { "WL_IDLE_STATUS=0", "WL_NO_SSID_AVAIL=1", "WL_SCAN_COMPLETED=2", "WL_CONNECTED=3", "WL_CONNECT_FAILED=4", "WL_CONNECTION_LOST=5", "WL_DISCONNECTED=6"};
        if(setup_wifi(deviceInfo)) {
          Serial.println("WiFi re-connect success.");
          return;
        }else {
          Serial.println("WiFi re-connect fail, wait 15 seconds");
          delay(15 * 1000);              // 一次没连上就休息15秒再继续
        }
      }
      Serial.println("WiFi re-connect fail 5 times, wait 5 minutes");
      delay(5 * 60 * 1000);             // 如果连接5次还没连上,那就休息5分钟再继续
    }
    Serial.println("WiFi re-connect fail, wait 25 minutes");
    delay(25 * 60 * 1000);             // 如果连接25次还没连上,那就休息25分钟再继续
  }
}

// http://www.esp8266.com/viewtopic.php?p=25466
String ipToString(IPAddress ip){
  String s="";
  for (int i=0; i<4; i++)
    s += i  ? "." + String(ip[i]) : String(ip[i]);
  return s;
}
/*****************4. code for wifi module end*****************/




/*****************5. code for moisture module start*****************/

void soilMoisture() {  
  soilMoistureValue[0] = digitalRead(MH_DO);
  soilMoistureValue[1] = analogRead(MH_A0);
  //串口返回测量数据  
//  Serial.print("Moisture=");  
//  Serial.print(analogRead(MH_A0));//读取AO的数值  
//  Serial.print("|DO=");  
//  Serial.println(digitalRead(MH_DO));//读取DO的数值  
//  delay(5 * 1000);  
}  

/*****************5. code for moisture module end*****************/
